﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;

namespace ChatClient
{
    public partial class ClientForm : Form, Chat.IMessageDelegate, ISettingsPanelDelegate, IOutgoingMessageHandler
    {
        // Private constants

        private const int kDefaultFormWidth = 800;
        private const int kDefaultFormHeight = 600;

        private const int kMarginBetweenItems = 5;
        private const int kLeftItemsOffset = 4;
        private const int kRightItemsOffset = 18;
        private const int kTopItemsOffset = 2;
        private const int kBottomItemsOffset = 2;

        private const int kGroupBoxVerticalBoundsWidth = 2;
        private const int kGroupBoxHorizontalBoundsWidth = 40;

        // Private data

        private Chat.Connection m_connection = null;

        private TabControl m_roomTabControl = null;
        private SettingsPanel m_settingsPanel = null;

        private enum RequestType
        {
            JoinRoomRequest,
            LeaveRoomRequest,
            OutgoingMessageRequest
        };

        private struct OugoingMessageRequest
        {
            public String receiver;
            public String message;
        };

        private Queue<KeyValuePair<RequestType, object>> m_requestsQueue = null;

        // Constructor

        public ClientForm()
        {
            InitializeComponent();

            this.Size = new Size(kDefaultFormWidth, kDefaultFormHeight);

            m_settingsPanel = new SettingsPanel(this, kLeftItemsOffset, kTopItemsOffset, kDefaultFormWidth - kLeftItemsOffset - kRightItemsOffset - kGroupBoxVerticalBoundsWidth, this);

            m_roomTabControl = new TabControl();
            m_roomTabControl.Parent = this;
            m_roomTabControl.Location = new System.Drawing.Point(kLeftItemsOffset, m_settingsPanel.Bottom + kMarginBetweenItems);
            m_roomTabControl.SelectedIndex = 0;
            m_roomTabControl.Size = new System.Drawing.Size(
                kDefaultFormWidth - kLeftItemsOffset - kRightItemsOffset,
                kDefaultFormHeight - m_settingsPanel.Height - kTopItemsOffset - kBottomItemsOffset - kMarginBetweenItems - kGroupBoxHorizontalBoundsWidth);
            m_roomTabControl.MouseClick += new MouseEventHandler((object sender, MouseEventArgs e) =>
            {
                TabControl.TabPageCollection tabs = m_roomTabControl.TabPages;
                if (e.Button == MouseButtons.Middle)
                {
                    ChatRoomPage page = tabs.Cast<ChatRoomPage>().Where((t, i) => m_roomTabControl.GetTabRect(i).Contains(e.Location))
                                                                 .First();
                    LeaveRoom(page.GetRoomName());
                }
            });

            m_requestsQueue = new Queue<KeyValuePair<RequestType, object>>();
        }

        // Private methods

        private delegate void PerformActionOnMainThreadDelegate(object action);

        private void PerformActionOnMainThread(object action)
        {
            System.Diagnostics.Debug.Assert(action is Action);

            if (InvokeRequired)
            {
                Invoke(new PerformActionOnMainThreadDelegate(this.PerformActionOnMainThread), action);
            }
            else
            {
                ((Action) action)();
            }
        }

        private void ShowErrorMessage(String message)
        {
            MessageBox.Show(message, "Error");
        }

        private bool JoinRoom(String roomName)
        {
            if (!Chat.ChatData.IsValidData(Chat.ChatData.Key.RoomId, roomName))
            {
                ShowErrorMessage("Invalid room name");
                return false;
            }

            m_connection.SendMessage(m_settingsPanel.CurrentUsername(), "@all", "/join " + roomName);
            m_requestsQueue.Enqueue(new KeyValuePair<RequestType, object>(RequestType.JoinRoomRequest, roomName));

            return true;
        }

        private bool LeaveRoom(String roomName)
        {
            if (roomName.Equals("@all"))
            {
                return false;
            }
               
            if (PageForRoom(roomName) != null)
            {
                m_requestsQueue.Enqueue(new KeyValuePair<RequestType, object>(RequestType.LeaveRoomRequest, roomName));
                m_connection.SendMessage(m_settingsPanel.CurrentUsername(), "@all", "/leave " + roomName);
                return true;
            }

            return false;
        }

        private void AddPageForRoom(String roomName)
        {
            ChatRoomPage page = PageForRoom(roomName);

            if (page != null)
            {
                return;
            }

            page = new ChatRoomPage(roomName, m_settingsPanel.CurrentUsername(), this);
            m_roomTabControl.TabPages.Add(page);
        }

        private void RemovePageForRoom(String roomName)
        {
            ChatRoomPage page = PageForRoom(roomName);

            if (page == null)
            {
                return;
            }

            m_roomTabControl.TabPages.Remove(page);
        }

        private ChatRoomPage PageForRoom(String roomName)
        {
            for (int i = 0; i < m_roomTabControl.TabPages.Count; i++)
            {
                System.Diagnostics.Debug.Assert(m_roomTabControl.TabPages[i] is ChatRoomPage);

                String pageRoomName = ((ChatRoomPage) m_roomTabControl.TabPages[i]).GetRoomName();

                if (pageRoomName.Equals(roomName))
                {
                    return (ChatRoomPage) m_roomTabControl.TabPages[i];
                }
            }

            return null;
        }

        // Events handlers

        private void ClientFormLoad(object sender, EventArgs e)
        {
            m_settingsPanel.Focus();
        }

        // IMessageDelegate interface implementation

        public bool GotMessage(String senderId, String receiverId, String message)
        {
            PerformActionOnMainThread(new Action(() =>
            {
                if (Chat.ChatCommand.Command.MessageForRoom == Chat.ChatCommand.GetCommand(message))
                {
                    String roomName = Chat.ChatCommand.GetCommandArgument(message);
                    String messageText = Chat.ChatCommand.GetMessage(message);

                    ChatRoomPage destinationRoom = PageForRoom(roomName);

                    if (destinationRoom == null)
                    { // already joined to room, but have no tab
                        AddPageForRoom(roomName);
                        destinationRoom = PageForRoom(roomName);
                    }

                    destinationRoom.OnIncomingMessage(senderId, messageText);
                }
                else
                {
                    ShowErrorMessage("Private messages are not supported in this version, please update your client");
                }
            }));

            return true;
        }

        public void GotDisconnectRequest(Chat.Connection connection)
        {
            m_connection = null;

            PerformActionOnMainThread(new Action(() =>
                {
                    ShowErrorMessage("Disconnected from server");
                    OnDisconnectAction();
                })
           );
        }

        public void GotDeliveryStatus(Chat.Connection connection, Chat.ChatData.DeliveryStatus status)
        {
            if (status == Chat.ChatData.DeliveryStatus.UserNotFound)
            {
                ShowErrorMessage("Receiver not found");
                m_requestsQueue.Dequeue();
            }
            else
            {
                PerformActionOnMainThread(new Action(() =>
                {
                    KeyValuePair<RequestType, object> request = m_requestsQueue.Dequeue();

                    switch (request.Key)
                    {
                        case RequestType.JoinRoomRequest:
                            AddPageForRoom((String)request.Value);
                            break;

                        case RequestType.LeaveRoomRequest:
                            RemovePageForRoom((String)request.Value);
                            break;

                        case RequestType.OutgoingMessageRequest:
                            String requestRoomName = ((OugoingMessageRequest)request.Value).receiver;

                            ChatRoomPage destinationRoom = PageForRoom(requestRoomName);

                            if (destinationRoom != null)
                            {
                                destinationRoom.OnOutgoingMessage(((OugoingMessageRequest)request.Value).message);
                            }
                            break;

                        default:
                            System.Diagnostics.Debug.Assert(false); // Some new request types added?
                            break;
                    }


                    if (request.Key == RequestType.JoinRoomRequest)
                    {
                    }
                    else if (request.Key == RequestType.OutgoingMessageRequest)
                    {
                    }
                    else
                    {
                    }
                }));
            }
        }

        private void OnResize(object sender, EventArgs e)
        {
            if (m_settingsPanel == null || m_roomTabControl == null)
            {
                return;
            }

            m_settingsPanel.Width = Width - kLeftItemsOffset - kRightItemsOffset - kGroupBoxVerticalBoundsWidth;
            m_roomTabControl.Size = new System.Drawing.Size(
                Width - kLeftItemsOffset - kRightItemsOffset,
                Height - m_settingsPanel.Height - kTopItemsOffset - kBottomItemsOffset - kMarginBetweenItems - kGroupBoxHorizontalBoundsWidth);
        }

        // ISettingsPanelDelegate implementation

        public void OnConnectAction(String serverAddress, String username)
        {
            if (!Chat.ChatData.IsValidData(Chat.ChatData.Key.SenderId, username))
            {
                ShowErrorMessage("Invalid username");
                return;
            }

            m_settingsPanel.SetConnectedState();

            System.Threading.Tasks.Task.Factory.StartNew(() =>
            {
                try
                {
                    if ((m_connection = Chat.Client.Connect(serverAddress, username, this)) == null)
                    {
                        ShowErrorMessage("Could not connect to server");
                        PerformActionOnMainThread(new Action(() =>
                        {
                            m_settingsPanel.SetDisconnectedState();
                        })
                        );
                        return;
                    }

                    JoinRoom("@all");
                }
                catch (Chat.UsernameTakenException)
                {
                    PerformActionOnMainThread(new Action(() =>
                    {
                        ShowErrorMessage("Username already taken");
                        m_connection = null;
                        m_settingsPanel.SetDisconnectedState();
                        m_settingsPanel.Focus();
                    })
                    );
                    return;
                }
            } // () => lambda
            ); // System.Threading.Tasks.Task.Factory.StartNew()
        }

        public void OnDisconnectAction()
        {
            if (m_connection != null)
            {
                m_connection.Disconnect();
            }

            m_settingsPanel.SetDisconnectedState();
            m_settingsPanel.Focus();
            m_roomTabControl.TabPages.Clear();
        }

        public void OnJoinRoomAction(String roomName)
        {
            System.Diagnostics.Debug.Assert(m_connection != null);
            System.Diagnostics.Debug.Assert(roomName != String.Empty);

            if (roomName[0] != '@')
                roomName = "@" + roomName;

            System.Diagnostics.Debug.Assert(roomName.Length > 1);

            JoinRoom(roomName);
        }

        // IOutgoingMessageHandler implementation

        public void OnMessage(String sender, String receiver, String message)
        {
            if (message.Equals(""))
            {
                return;
            }

            if (m_connection == null)
            {
                return;
            }

            if (receiver.Equals(m_settingsPanel.CurrentUsername()))
            {
                ShowErrorMessage("Can't send message to yourself");
                return;
            }

            if (!Chat.ChatData.IsValidData(Chat.ChatData.Key.ReceiverId, receiver) &&
                !Chat.ChatData.IsValidData(Chat.ChatData.Key.RoomId, receiver))
            {
                ShowErrorMessage("Invalid receiver");
                return;
            }

            if (Chat.ChatCommand.GetCommand(message) == Chat.ChatCommand.Command.JoinRoom)
            {
                JoinRoom(Chat.ChatCommand.GetCommandArgument(message));
                return;
            }

            if (Chat.ChatCommand.GetCommand(message) == Chat.ChatCommand.Command.LeaveRoom)
            {
                String roomName = Chat.ChatCommand.GetCommandArgument(message);
                LeaveRoom(roomName);
                return;
            }

            OugoingMessageRequest request = new OugoingMessageRequest();
            request.message = message;
            request.receiver = receiver;
            m_requestsQueue.Enqueue(new KeyValuePair<RequestType, object>(RequestType.OutgoingMessageRequest, request));

            m_connection.SendMessage(sender, receiver, message);
        }
    }
}
